iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
自我挑戰組

Kotlin & Flutter App 開發比較思考日誌系列 第 14

[鐵人賽 Day 14] Kotlin & Flutter 元件比較(二) - ListView 如何動態更新內容

  • 分享至 

  • xImage
  •  

目的

先前範例比較 Kotlin & Flutter ListView 動態更新內容方式

Kotlin

修改以下四個檔案:

  1. MainActivity.kt
  2. activity_main.xml
  3. colors.xml
  4. MyAdapter.kt
實際執行完成畫面
https://ithelp.ithome.com.tw/upload/images/20230918/20162686TWG9LJKeYX.jpg

以下為 MainActivity.kt 檔案內容:

  • 監聽更新資料按鈕,點擊按鈕後,由 ViewModel 刷新 ListVeiw 資料。
package com.example.kotlin_demo

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.Button
import android.widget.ListView
import android.widget.TextView
import androidx.core.content.ContextCompat
import kotlin.math.absoluteValue
import kotlin.random.Random

class MainActivity : AppCompatActivity() {
    private val viewModel = MyViewModel()
    private val bookData = mutableListOf<BookInfor>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val listView : ListView = findViewById<ListView>(R.id.listView)



        for(bookNum in 1..9){
            bookData.add(BookInfor("MyBook$bookNum","Author$bookNum","2023-09-0$bookNum"))
        }

        viewModel.addList(bookData)

        val adapter = MyAdapter(this)

        //當 viewModel.BookList 資料被更新時,重新設定 Adapter 資料
        viewModel.BookList.observe(this,{
            adapter.setData(it)
        })
        listView.adapter = adapter

        val textView: TextView = findViewById<TextView>(R.id.TV);
        listView.setOnItemClickListener(object:AdapterView.OnItemClickListener{
            override fun onItemClick(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
                textView.setText("${p0!!.adapter.getItem(p2)}")
            }

        })

        ///更新資料按鈕
        val buttonForUpDateData: Button = findViewById<Button>(R.id.Button);
        buttonForUpDateData.apply {
            setText("Update ListView Data")
            isAllCaps = false;
            setBackgroundColor(ContextCompat.getColor(this.context, R.color.orangeAccent))
            setTextColor(ContextCompat.getColor(this.context, R.color.white))
            setOnClickListener(object : View.OnClickListener {
                override fun onClick(p0: View?) {
                    ///隨機取得20以下其中一個數字
                    var iInitialRandomNum: Int = Random.nextInt(20)
                    createSampleData(iInitialRandomNum, iInitialRandomNum + 9)
                }
            })
        }


    }
    //建立範例資料
    /// iInitialNum (初始值) , iMaxNum (最大值)
    fun createSampleData(iInitialNum: Int = 1, iMaxNum: Int = 9) {
        val list = mutableListOf<BookInfor>()

        ///依照 初始值與最大值 建立連續資料
        for (bookNum in iInitialNum..iMaxNum) {
            list.add(
                BookInfor(
                    "MyBook$bookNum",
                    "Author$bookNum",
                    if (bookNum < 10) "2023-09-0$bookNum" else "2023-09-$bookNum"
                )
            )
        }
        ///清除目前頁面 ListView 資料
        bookData.clear()

        ///設定目前頁面 ListView 資料為新建的範例資料
        bookData.addAll(list)

        ///更新ViewModel 的 BookList 書籍資料
        viewModel.BookList.value = bookData
    }

}

以下為 activity_main.xml 檔案內容:

  • 新增按鈕 layout。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button android:id = "@+id/Button"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/listView"
        />

    <TextView
        android:id = "@+id/TV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/Button"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

以下為 colors.xml 檔案內容:

  • 新增橘色(orangeAccent)。
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple_200">#FFBB86FC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_700">#FF018786</color>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
    <color name="amber">#FFFFC107</color>
    <color name="lightGreen">#FF8BC34A</color>
    <color name="yellow">#FFFFEA00</color>
    <color name="orangeAccent">#FFFFAB40</color>
</resources>

以下為 MyAdapter.kt 檔案內容:

  • 先清除 Adapter 資料,再刷新資料。
fun setData(itemList: List<BookInfor>) {
        /// 清除資料
        clear()
        addAll(itemList)
        notifyDataSetChanged()
    }

Flutter

  • main.dart
    • 新增更新資料按鈕
    • GestureDetector 元件監聽按鈕點擊事件。
    • 測到點擊事件隨機選 20 以下的數字為起始數值,進而建立新範例資料。
    • 將新範例資料傳入 bookInfoList 變數。
    • 使用 setState((){});:由 新的bookInfoList 變數建立新的 ListView 元件。
實際執行結果畫面
https://ithelp.ithome.com.tw/upload/images/20230918/20162686BMapqrI4PS.jpg
import 'dart:math';

import 'package:flutter/material.dart';

import 'BookInfor.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<StatefulWidget> {
  ///設定文字大小
  double dTextSize = 20;

  ///書籍資訊相關資料
  List<BookInfor> bookInfoList = [];

  //需顯示的元件
  Widget? _wPrintedItem;

  @override
  void initState() {
    super.initState();
    setData();
  }

  @override
  Widget build(BuildContext context) {

    List<Widget> widgetList = [
      Expanded(child: _wListView(),flex: 7,),
      Expanded(child: _wUpdateDataButton()),
    ];
    widgetList.add(Expanded(child: _wPrintedItem ?? Container(),flex: 2,));

    //建立 ListView
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        home: Scaffold(
            body: Column(
          children: widgetList,
        )));
  }

  ///ListView 元件
  Widget _wListView() {
    return ListView(
      //依 資料 List 長度並轉為 元件 List 資料結構
      children: List.generate(bookInfoList.length , (index) => _wListItem(bookInfoList[index])),
    );
  }

  ///ListView item 元件
  /// - [sNum] : 帶入的數字
  Widget _wListItem(BookInfor data) {
    //Column 元件將 child 元件由上到下垂直排列
    return InkWell(
        onTap: () {
          setState(() {
            _wPrintedItem = _wPrintTappedItem(data);
          });
        },
        child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
          //Card 元件包著書籍資訊,
          //注意 Card 元件預設有 margin, 需要設為0才會完全貼齊其他元件
          Card(
            margin: EdgeInsets.zero,
            child: _wBookItem(data),
            shape:
                RoundedRectangleBorder(borderRadius: BorderRadius.circular(11)),
          ),
          //分隔線
          const Divider(
            thickness: 1,
            height: 0.5,
          ),
        ]));
  }

  ///書籍 item 元件
  /// - [sNum] : 需帶入的數字
  Widget _wBookItem(BookInfor bookData) {
    //Container 元件會依據 child 元件大小包覆,方便設定 child 元件的 padding 或 margin
    return Container(
        padding: EdgeInsets.symmetric(vertical: 5, horizontal: 5),
        //Column 元件將 _wBookNameAuthor 和 _wBookBuyDate 元件由垂直排列
        // MainAxisSize.min 設定代表 只依 child 元件大小包住 child 元件,不會延伸到最大螢幕畫面
        // CrossAxisAlignment.start 設定代表 垂直靠左排列
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _wBookNameAuthor(bookData.sBookName, bookData.sBookAuthor),
            _wBookBuyDate(bookData.sBookBuyDate),
          ],
        ));
  }

  ///書籍名稱與作者 元件
  /// - [sName] : 書籍名稱
  /// - [sAuthor] : 書籍作者
  Widget _wBookNameAuthor(String sName, String sAuthor) {
    return Container(
        child: Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        // 文字元件
        Text(
          sName,
          style: TextStyle(fontSize: dTextSize),
        ),
        Container(
          width: 10,
        ),
        Text(
          sAuthor,
          style: TextStyle(fontSize: dTextSize),
        ),
      ],
    ));
  }

  ///書籍購買日期元件
  /// - [sDate] : 購買日期
  Widget _wBookBuyDate(String sDate) {
    return Container(
        child: Text(
      sDate,
      style: TextStyle(fontSize: dTextSize),
    ));
  }

  ///設定 ListView 資料
  setData() {
    setSampleData();
  }

  ///印出 ListView 被點擊的 Item 資料
  Widget _wPrintTappedItem(dynamic Data) {
    return Container(child: Text("$Data"));
  }

  /// 更新資料按鈕元件
  Widget _wUpdateDataButton(){

    ///初始化隨機數字
    int iIntialRandomNum = Random().nextInt(20).abs();

    return GestureDetector(
      onTap: () {
        setSampleData(iInitialNum: iIntialRandomNum, iMaxNum: iIntialRandomNum + 10);
        setState(() {});
      },
      child: Container(
        alignment: Alignment.center,
        constraints: BoxConstraints(maxWidth: 150),
        decoration: BoxDecoration(
            color: Colors.orangeAccent,
            borderRadius: BorderRadius.circular(15)),
        child: Text(
          "Update ListView Data",
          style: TextStyle(color: Colors.white,),
          textAlign: TextAlign.center
        ),
      ),
    );
  }

  /// 建立範例資料
  /// - 由起始數值和結束數值決定範例資料
  /// - [iInitialNum] : 起始數值
  /// - [iMaxNum] : 結束數值
  setSampleData({int iInitialNum = 1, int iMaxNum = 10}) {
    List<BookInfor> list = [];
    if (list.isEmpty) {
      for (int i = iInitialNum; i < iMaxNum; i++) {
        list.add(BookInfor("MyBook$i", "Author$i", i < 10 ? "2023-09-0$i" : "2023-09-$i"));
      }
      bookInfoList.clear();
      bookInfoList.addAll(list);
    }
  }
}

比較 Kotlin & Flutter 動態更新 ListView 資料方式

Kotlin Flutter
如何動態更新 ListView 資料 observer callback 刷新 ListView Adapter 資料 更新 ListView 資料來源變數內容,並 setState((){}); 生成新 ListView 元件

上一篇
[鐵人賽 Day 13] Kotlin & Flutter 元件比較(二) - ListView 如何知道被點擊的項目為何
下一篇
[鐵人賽 Day 15] Kotlin & Flutter 元件比較(三) - 佈局相關常用元件名稱
系列文
Kotlin & Flutter App 開發比較思考日誌30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言